// Created by: Peter KURNEV
// Copyright (c) 2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.


#include <BRep_Tool.hxx>
#include <BRepCheck.hxx>
#include <BRepCheck_ListOfStatus.hxx>
#include <BRepCheck_Solid.hxx>
#include <BRepClass3d_SolidClassifier.hxx>
#include <Geom_Curve.hxx>
#include <gp_Pnt.hxx>
#include <Standard_Transient.hxx>
#include <NCollection_Vector.hxx>
#include <Precision.hxx>
#include <Standard_Type.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS_Builder.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Solid.hxx>
#include <TopTools_MapOfShape.hxx>

IMPLEMENT_STANDARD_RTTIEXT(BRepCheck_Solid,BRepCheck_Result)

//
class BRepCheck_HSC;
DEFINE_STANDARD_HANDLE(BRepCheck_HSC, Standard_Transient)
//=======================================================================
//class    : BRepCheck_HSC
//purpose  :
//=======================================================================
class BRepCheck_HSC : public Standard_Transient {
 public: 
  //
  Standard_EXPORT
    BRepCheck_HSC(){
    };
  //
  Standard_EXPORT
    virtual ~BRepCheck_HSC(){
  };
  //
  Standard_EXPORT
    BRepClass3d_SolidClassifier& SolidClassifier(){
      return mySC;
    };
  //
  DEFINE_STANDARD_RTTI_INLINE(BRepCheck_HSC,Standard_Transient);

 protected:
  BRepClass3d_SolidClassifier mySC;
};

//
//=======================================================================
//class    : BRepCheck_ToolSolid
//purpose  : 
//=======================================================================
class BRepCheck_ToolSolid  {

 public:
  DEFINE_STANDARD_ALLOC

  BRepCheck_ToolSolid() {
    myIsHole=Standard_False;
    myPntTol=Precision::Confusion();
    myPnt.SetCoord(-1.,-1.,-1.);
  };
   
  virtual ~BRepCheck_ToolSolid() {
  };
  // 
  void SetSolid(const TopoDS_Solid& aZ) {
    mySolid=aZ;
  };
  //
  const TopoDS_Solid& Solid()const {
    return mySolid;
  };
  //
  Standard_Boolean IsHole() const {
    return myIsHole;
  };
  //
  const gp_Pnt& InnerPoint() {
    return myPnt;
  }
  //
  Standard_Real CheckTol() const {
    return myPntTol;
  };
  //
  // IsOut
  Standard_Boolean IsOut(BRepCheck_ToolSolid& aOther)  {
    Standard_Boolean bFlag;
    TopAbs_State aState;
    //
    BRepClass3d_SolidClassifier& aSC=myHSC->SolidClassifier();
    //
    aSC.Perform(aOther.InnerPoint(), aOther.CheckTol());
    aState=aSC.State();
    bFlag=(aState==TopAbs_OUT);
    //
    return bFlag;
  };
  //
  // Init
  void Init() {
    Standard_Real aT, aT1, aT2, aPAR_T;
    TopExp_Explorer aExp;
    //
    // 0.myHSC
    myHSC=new BRepCheck_HSC();
    //
    BRepClass3d_SolidClassifier& aSC=myHSC->SolidClassifier();
    // 1. Load
    aSC.Load(mySolid);
    //
    // 2. myIsHole
    aSC.PerformInfinitePoint(::RealSmall());
    myIsHole=(aSC.State()==TopAbs_IN);
    // 
    // 3. myPnt
    aPAR_T=0.43213918; // 10*e^(-PI)
    aExp.Init(mySolid, TopAbs_EDGE);
    for (; aExp.More();  aExp.Next()) {
      const TopoDS_Edge& aE=*((TopoDS_Edge*)&aExp.Current());
      if (!BRep_Tool::Degenerated(aE)) {
        Handle(Geom_Curve) aC3D=BRep_Tool::Curve(aE, aT1, aT2);
        aT=(1.-aPAR_T)*aT1 + aPAR_T*aT2;
        myPnt=aC3D->Value(aT);
        myPntTol = BRep_Tool::Tolerance(aE);
        break;
      }
    }
  };
  //
 protected:
  Standard_Boolean myIsHole;
  gp_Pnt myPnt; 
  Standard_Real myPntTol;
  TopoDS_Solid mySolid;
  Handle(BRepCheck_HSC) myHSC;
};
//
typedef NCollection_Vector<BRepCheck_ToolSolid>
  BRepCheck_VectorOfToolSolid;
//

//=======================================================================
//function : BRepCheck_Solid
//purpose  : 
//=======================================================================
BRepCheck_Solid::BRepCheck_Solid (const TopoDS_Solid& S)
{
  Init(S);
}
//=======================================================================
//function : Blind
//purpose  : 
//=======================================================================
void BRepCheck_Solid::Blind()
{
  if (!myBlind) {
    // nothing more than in the minimum
    myBlind = Standard_True;
  }
}
//=======================================================================
//function : InContext
//purpose  : 
//=======================================================================
void BRepCheck_Solid::InContext(const TopoDS_Shape& )
{
}
//=======================================================================
//function : Minimum
//purpose  : 
//=======================================================================
void BRepCheck_Solid::Minimum()
{
  if (myMin) {
    return;
  }
  myMin = Standard_True;
  //
  Standard_Boolean bFound, bIsHole, bFlag;
  Standard_Integer i, j, aNbVTS, aNbVTS1, iCntSh, iCntShInt;
  TopoDS_Solid aZ;
  TopoDS_Iterator aIt, aItF;
  TopoDS_Builder aBB;
  TopExp_Explorer aExp;
  TopTools_MapOfShape aMSS;
  TopAbs_Orientation aOr; 
  BRepCheck_VectorOfToolSolid aVTS;

  Handle(BRepCheck_HListOfStatus) aNewList = new BRepCheck_HListOfStatus();
  BRepCheck_ListOfStatus& aLST = **myMap.Bound (myShape, aNewList);
  aLST.Append(BRepCheck_NoError);
  //
  //-------------------------------------------------
  // 1. InvalidImbricationOfShells
  bFound=Standard_False;
  aExp.Init(myShape, TopAbs_FACE);
  for (; !bFound && aExp.More(); aExp.Next()) {
    const TopoDS_Shape& aF=aExp.Current();
    if (!aMSS.Add(aF)) {
      BRepCheck::Add (aLST, BRepCheck_InvalidImbricationOfShells);
      bFound=!bFound;
    }
  } 
  //
  //-------------------------------------------------
  // 2. 
  //    - Too many growths,
  //    - There is smt of the solid that is out of solid
  iCntSh=0;
  iCntShInt=0;
  aIt.Initialize(myShape);
  for (; aIt.More(); aIt.Next()) {
    const TopoDS_Shape& aSx=aIt.Value();
    //
    if (aSx.ShapeType()!=TopAbs_SHELL) {
      aOr=aSx.Orientation();
      if (aOr!=TopAbs_INTERNAL) {
        BRepCheck::Add (aLST, BRepCheck_BadOrientationOfSubshape);
      } 
      continue;
    }
    //
    const TopoDS_Shell& aSh=*((TopoDS_Shell*)&aSx);
    //
    // Skip internal shells
    bFound=Standard_False;
    aItF.Initialize(aSh);
    for (; !bFound && aItF.More(); aItF.Next()) {
      const TopoDS_Shape& aF=aItF.Value();
      aOr=aF.Orientation();
      if (aOr==TopAbs_INTERNAL) {
        bFound=!bFound;
      }
    }
    if (bFound) {
      ++iCntShInt;
      continue;
    }
    //
    ++iCntSh;
    //
    // Skip not closed shells
    if (!BRep_Tool::IsClosed(aSh)) {
      continue;
    }
    //
    aBB.MakeSolid(aZ);
    aBB.Add(aZ, aSh);
    //
    BRepCheck_ToolSolid aTS;
    //
    aTS.SetSolid(aZ);
    aVTS.Append(aTS);
  }//for (; aIt.More(); aIt.Next()) {
  //
  if (!iCntSh && iCntShInt) {
    // all shells in the solid are internal
    BRepCheck::Add (aLST, BRepCheck_BadOrientationOfSubshape);
  }
  //
  aNbVTS=aVTS.Size();
  if (aNbVTS<2) {
    return;
  }
  //
  aNbVTS1=0;
  for (i=0; i<aNbVTS; ++i) {
    BRepCheck_ToolSolid& aTS=aVTS(i);
    //
    aTS.Init();
    bIsHole=aTS.IsHole();
    if (!bIsHole) {
      ++aNbVTS1;
      if (aNbVTS1>1) {
        // Too many growths
        BRepCheck::Add (aLST, BRepCheck_EnclosedRegion);
        break;
      }
    }
  }
  //
  bFound=Standard_False;
  aNbVTS1=aNbVTS-1;
  for (i=0; !bFound && i<aNbVTS1; ++i) {
    BRepCheck_ToolSolid& aTSi=aVTS(i);
    //
    for (j=i+1; !bFound && j<aNbVTS; ++j) {
      BRepCheck_ToolSolid& aTSj=aVTS(j);
      //
      bFlag=aTSi.IsOut(aTSj);
      if (bFlag) {
        // smt of solid is out of solid
        BRepCheck::Add (aLST, BRepCheck_SubshapeNotInShape);
        bFound=!bFound;
      }
    }
  }
  //
  //myMin = Standard_True;
}
